﻿using System.ComponentModel;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Microsoft.Data.Entity.Design.Extensibility;
using System;

namespace Liekhus.EntityFramework.XAFExtensions.CustomAttributes
{
    /// <summary>
    /// Base class inherited by all Custom Entity Properties in this project which are automatically added on to 
    /// each Property of each Entity in the Model Designer (.edmx) in order to generate DevExpress XAF/XPO attributes 
    /// on each property of each Business Object/Base Object class represented by each Entity in the Model Designer.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    [DefaultPropertyAttribute("PropertyValue")]
    public abstract class BaseCustomAttribute<T>
    {
        #region Modular Variables
        /// <summary>
        /// Default namespace schema used in the EF Model Designer to distinquish these attributes from another extension's attributes
        /// </summary>
        internal static readonly string _namespace = "http://schemas.liekhus.com/entityframework/xaf/customattributes";
        /// <summary>
        /// The XML Name representation (including namespace) of the Attribute used in the Entity Model (.edmx) file to 
        /// of the custom element used to hold the value of the Unique Property Name used by the custom property.
        /// &lt;BaseCustomAttribute-Size PropertyName="Size"&gt;
        /// </summary>
        internal static XName _xnPropertyName = XName.Get("PropertyName", _namespace);
        /// <summary>
        /// The XML Name representation (including namespace) of the Attribute used in the Entity Model (.edmx) file to
        /// of the custom element used to hold the value of the string representation of the CLR Type that the custom 
        /// property represents (i.e. System.String, System.Int32, etc)
        /// &lt;BaseCustomAttribute-Size PropertyType="System.Int32"&gt;
        /// </summary>
        internal static XName _xnPropertyType = XName.Get("PropertyType", _namespace);
        /// <summary>
        /// The XML Name representation (including namespace) of the Attribute used in the Entity Model (.edmx) file to 
        /// of the custom element used to hold the value of the Format String to be used when generating the Attribute 
        /// on each property in the Business Object/Base Object class generated.
        /// &lt;BaseCustomAttribute-Size PropertyFormat="[Size({value}]"&gt;
        /// </summary>
        internal static XName _xnPropertyFormat = XName.Get("PropertyFormat", _namespace);
        /// <summary>
        /// The Properties Group Name/Category to which the custom properties belong as grouped 
        /// in the VS IDE Properties window for each Property of an Entity in the Model Designer
        /// </summary>
        internal const string _category = "DevExpress XAF Properties";
        /// <summary>
        /// XML Element representing the current Entity being processed for this custom property 
        /// to generate the XAF/XPO attribute
        /// </summary>
        private XElement _parent;
        /// <summary>
        /// The Visual Studio Context in which the Property Extension is being processed
        /// </summary>
        private PropertyExtensionContext _context;
        /// <summary>
        /// 
        /// </summary>
        private bool _isDisplayPropertyInEdm;
        /// <summary>
        /// 
        /// </summary>
        private IList<string> _validPropertyTypeNames;
        #endregion

        #region Constructor
        /// <summary>
        /// Default Constructor
        /// </summary>
        /// <param name="parent">XML Element representing the current Entity being processed for this custom property  to generate the XAF/XPO attribute</param>
        /// <param name="context">The Visual Studio Context in which the Property Extension is being processed</param>
        public BaseCustomAttribute(XElement parent, PropertyExtensionContext context)
        {
            _isDisplayPropertyInEdm = true;
            _context = context;
            _parent = parent;
            _validPropertyTypeNames = new List<string>();
        }
        #endregion

        #region Properties
        /// <summary>
        /// Abstract property which when inherited will represent a unique Property Name 
        /// used by the custom property for identification (i.e. DisplayFormat, Size, etc)
        /// </summary>
        [BrowsableAttribute(false)]
        public abstract string PropertyName
        {
            get;
        }
        /// <summary>
        /// Abstract property which when inherited will contain the a Formatted string to be used 
        /// when generating the Attribute on each property in the Business Object/Base Object class generated.
        /// The Format string placeholders {value} and {entity} will be replaced by  the PropertyValue Property 
        /// and the PropertyName Property respectively (see the XAFGenerator.GetFormattedAttribute() for more details).
        /// </summary>
        [BrowsableAttribute(false)]
        public abstract string PropertyFormat
        {
            get;
        }
        /// <summary>
        /// The Default Value to be used if no value is provided in the PropertyValue Property
        /// </summary>
        [BrowsableAttribute(false)]
        public virtual T DefaultValue
        {
            get { return default(T); }
        }
        /// <summary>
        /// The name to be used for the XML Element in the Entity Model when the custom property is added.
        /// This value is currently BaseCustomAttribute-&lt;PropertyName&gt; for easy processing when generating
        /// the XAF/XPO classes.
        /// </summary>
        private string ElementName
        {
            get
            {
                return string.Format("{0}-{1}", "BaseCustomAttribute", PropertyName);
            }
        }

        protected T propertyValue;
        /// <summary>
        /// This property is saved in the conceptual content of an .edmx document in the following format:
        /// &lt;EntityType&gt;
        ///  &lt;!-- other entity properties --&gt;
        ///  &lt;MyNewProperty xmlns="http://schemas.liekhus.com/PropertyValue"&gt;True&lt;/MyNewProperty&gt;
        /// &lt;/EntityType&gt;
        /// </summary>
        [Category(BaseCustomAttribute<T>._category)]
        public virtual T PropertyValue
        {
            // Read and return the property value from the structured annotation in the EntityType element.
            get
            {
             
                bool isValueFound;
                XElement lastChild;

                propertyValue = this.DefaultValue;
                try
                {
                    isValueFound = false;

                    if (_parent.HasElements)
                    {
                        //Find the last Element of the current parent (Entity Property) which matches the current ElementName
                        //and which also had the same attribute value as stored in the PropertyName property (as there can be multiple
                        //of the same CustomProperty on an Entity.Property each with different values - Think of multiple Security 
                        //attributes on the same XPO property)
                        lastChild = _parent.Elements().Where<XElement>(element =>
                                                            element != null &&
                                                            element.Name == XName.Get(this.ElementName, _namespace) &&
                                                            element.Attribute(_xnPropertyName).Value == this.PropertyName).LastOrDefault();
                        if (lastChild != null)
                        {
                            // element exists, so get its value.
                            if (!string.IsNullOrEmpty(lastChild.Value.Trim()))
                            {
                                if (typeof(T).IsGenericType == false)
                                {
                                    propertyValue = (T)Convert.ChangeType(lastChild.Value.Trim(), typeof(T));
                                    isValueFound = true;
                                }
                                else
                                {
                                    Type tGenericBeta = Type.GetType(lastChild.Value.Trim());
                                    Type tGeneric = typeof(T);
                                    Type tGenericTypeDef = tGeneric.GetGenericTypeDefinition();
                                    //{Name = "CustomAttributeCodeProperty" FullName = "Liekhus.EntityFramework.XAFExtensions.CustomAttributes.CustomAttributeCodeProperty"}
                                    Type[] taGenericArgs = tGeneric.GetGenericArguments();
                                    Type tGenericConstructed = tGenericTypeDef.MakeGenericType(taGenericArgs);
                                    object oGenericInstance = Activator.CreateInstance(tGenericConstructed);
                                    propertyValue = (T)oGenericInstance;

                                    if (lastChild.HasElements == true)
                                    {
                                        //((IList)(T)oGenericInstance).a
                                    }

                                    isValueFound = true;
                                }
                            }
                        }
                    }

                    if (!isValueFound)
                    {
                        if (propertyValue != null)
                        {
                            //force the value to be written to the XML file so that the code generation can read it
                            // ALF - This is causing the majority of problems, I think default values will have to be handled differently
                            // it seems setting the value while in the Getter is causing some grief
                            // The other thought is that unless you go to the property of a Entity then the getter won't even run
                            // this could lead to data not being stored in the model couldn't it?
                            //this.PropertyValue = propertyValue;
                        }
                    }
                }
                catch (Exception ex)
                {
                    throw ex;
                }
                return propertyValue;
            }

            // Write the new property value to the structured annotation in the EntityType element.
            set
            {
                
                XElement customElement;
                XElement lastChild;
                bool bValOutput;

                propertyValue = value;

                try
                {
                    // Make changes to the .edmx document in an EntityDesignerChangeScope to enable undo/redo of changes.
                    using (EntityDesignerChangeScope scope = _context.CreateChangeScope(string.Format("Set {0} Property Value", this.PropertyName)))
                    {
                        customElement = null;
                        if (propertyValue != null)
                        {
                            if ((this.PropertyFormat.Contains("{value}") == false) &&
                                (bool.TryParse(propertyValue.ToString(), out bValOutput) == true) &&
                                (bValOutput == false))
                            {
                                //Don't create the customElement as we don't really want to persist it for this property
                                //as the PropertyFormat doesn't contain a {value} but the value is a boolean indicating
                                //if the custom attribute should be created (in this case it's false so it shouldn't be generated)
                            }
                            else
                            {
                                //Generate a "template" Xml Element using our Custom Property's ElementName and namespace and initialize
                                //it with the PropertyValue being passed in.  Then add on the attributes (metadata) which determine
                                //the Name, Type and Format string of data being persisted in the element
                                //i.e. <BaseCustomAttribute-Size PropertyName="" PropertyType="" PropertyFormat="">propertyValue</BaseCustomAttribute-Size>
                                customElement = new XElement(XName.Get(this.ElementName, _namespace), propertyValue);
                                customElement.Add(new XAttribute(_xnPropertyName, this.PropertyName));
                                customElement.Add(new XAttribute(_xnPropertyType, typeof(T).ToString()));
                                customElement.Add(new XAttribute(_xnPropertyFormat, this.PropertyFormat));
                            }
                        }

                        if (_parent.HasElements)
                        {
                            //Determine if an Element matching our custom property already exists as a child of the
                            //current Entity.Property being processed in order to add/update/remove settings accordingly
                            lastChild = _parent.Elements().Where<XElement>(element => element != null &&
                                                                    element.Name == XName.Get(this.ElementName, _namespace) &&
                                                                    element.Attribute(_xnPropertyName).Value == this.PropertyName).LastOrDefault();
                            if (lastChild != null)
                            {
                                // element already exists under the EntityType element, so update its value.
                                if (propertyValue != null)
                                {
                                    lastChild.SetValue(propertyValue);
                                    XAttribute attr = lastChild.Attributes().Where(item => item.Name == _xnPropertyFormat).FirstOrDefault();
                                    if (attr != null)
                                        attr.Value = this.PropertyFormat;
                                }
                                else
                                {
                                    //property value has been removed so let's remove the property
                                    lastChild.Remove();
                                }
                            }
                            else
                            {
                                if (customElement != null)
                                {
                                    // element does not exist, so create a new one as the last child of the EntityType element.
                                    _parent.Elements().Last().AddAfterSelf(customElement);
                                }
                            }
                        }
                        else
                        {
                            // The EntityType element has no child elements so create a new MyNewProperty element as its first child.
                            if (customElement != null)
                            {
                                _parent.Add(customElement);
                            }
                        }

                        // Commit the changes.
                        scope.Complete();
                    }
                }
                catch (Exception ex)
                {
                    throw ex;
                }

            }
        }
        /// <summary>
        /// Value indicating that the Custom Property should be displayed in 
        /// the Entity Data Model Designer property grid or not
        /// </summary>
        [BrowsableAttribute(false)]
        public bool DisplayPropertyInEDM
        {
            get { return _isDisplayPropertyInEdm; }
            protected set { _isDisplayPropertyInEdm = value; }
        }
        /// <summary>
        /// 
        /// </summary>
        [BrowsableAttribute(false)]
        protected IList<string> ValidPropertyTypeNames
        {
            get { return _validPropertyTypeNames; }
            set { _validPropertyTypeNames = value; }
        }
        #endregion

        #region Protected Methods
        /// <summary>
        /// This method will strip out the Custom Property elements from the Entity Model XElement parent 
        /// passed in on the constructor if it exists.  This functionality will allow for the removal of 
        /// Custom Property settings which are no longer valid for the Entity or Entity.Property.
        /// </summary>
        protected virtual void RemovedExistingCustomProperty()
        {
            IEnumerable<XElement> ieBaseCustomAttirbutes;
            return;
            
            // Make changes to the .edmx document in an EntityDesignerChangeScope to enable undo/redo of changes.
            using (EntityDesignerChangeScope scope = _context.CreateChangeScope(string.Format("Remove {0} Property Value", this.PropertyName)))
            {
                //Find any and all Elements for this custom property which exist in the parent XElement
                //and then remove them as they are not valid for this Entity or Entity.Property
                ieBaseCustomAttirbutes = _parent.Elements(XName.Get(this.ElementName, _namespace));

                if (ieBaseCustomAttirbutes.Count() > 0)
                {
                    ieBaseCustomAttirbutes.Remove();
                }

                // Commit the changes.
                scope.Complete();
            }

        }
        #endregion
    }
}
